using System;
using Moq;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class RecoveryProductionSpecification {
        private string SourceString;
        private Mock<ISyntaxErrorRecoverer> Recoverer;
        private ParseContext Context;
        private ParseEnvironment Environment;
        private char[] Source;
        private RecoveryProduction RecoveryProduction;
        private ParseResult ParseResult;
        private ITerm Term;
        private int ResultIndex = 4;
        private Mock<IProduction> Production;
        private PunctuationProduction RecoveryPoint;
        private ParseResult RecoveryParseResult;
        private ITerm ErrorTerm;
        private Mock<ISyntaxErrorLogger> SyntaxErrorLogger;

        [Test]
        public void ShouldAddRecoveryPoint () {
            Setup ();
            SetupCallbackParse (() => Recoverer.Verify(r => r.AddRecoveryPoint (RecoveryPoint)));

            RecoveryProduction.Parse (Source, 0, Context, SourceString, Environment);
        }

        [Test]
        public void ShouldReturnResultOfRescuingParseException () {
            Setup ();
            SetupRecoveredParse (RecoveryPoint);

            ParseResult result = RecoveryProduction.Parse (Source, 0, Context, SourceString, Environment);
            Assert.That (result, Is.SameAs (RecoveryParseResult));
        }

        [Test]
        public void ShouldRethrowExceptionIfDifferentRecoveryPointProduction () {
            Setup ();
            SetupRecoveredParse (new Mock<IProduction> ().Object);

            Assert.That (() => { RecoveryProduction.Parse (Source, 0, Context, SourceString, Environment); },
                         Throws.TypeOf (typeof (SyntaxErrorRecoveryException)));
        }

        [Test]
        public void ShouldReturnResultOfRuleToParse () {
            Setup ();
            SetupSuccessfulParse ();

            ParseResult result = RecoveryProduction.Parse (Source, 0, Context, SourceString, Environment);
            Assert.That (result, Is.SameAs (ParseResult));
        }

        [Test]
        public void ShouldAddErrorRecoveryWhenRecovered () {
            Setup ();
            SetupRecoveredParse (RecoveryPoint);

            RecoveryProduction.Parse (Source, 0, Context, SourceString, Environment);

            SyntaxErrorLogger.Verify (s => s.AddErrorRecovery (RecoveryProduction, 0, RecoveryParseResult));
        }

        private void Setup () {
            Recoverer = new Mock<ISyntaxErrorRecoverer> ();

            Production = new Mock<IProduction> ();
            Term = new Mock<ITerm> ().Object;
            RecoveryPoint = new PunctuationProduction (";");
            RecoveryProduction = new RecoveryProduction(Production.Object, RecoveryPoint);
            SourceString = "abcd;";
            Source = SourceString.ToCharArray ();
            Context = new ParseContext ();
            Environment = new ParseEnvironment ("filename", Recoverer.Object);
            SyntaxErrorLogger = new Mock<ISyntaxErrorLogger> ();
            Environment.SyntaxErrors = SyntaxErrorLogger.Object;
            ParseResult = new ParseResult (ResultIndex, Term, Context);
            ErrorTerm = new Mock<ITerm> ().Object;
            RecoveryParseResult = new ParseResult (ResultIndex, ErrorTerm, Context);
        }

        private void SetupSuccessfulParse () {
            Production
                .Setup (p => p.Parse (Source, 0, Context, SourceString, Environment))
                .Returns (ParseResult);
        }

        private void SetupCallbackParse (Action callback) {
            Production
                .Setup (p => p.Parse (Source, 0, Context, SourceString, Environment))
                .Callback (callback)
                .Returns (ParseResult);
        }

        private void SetupRecoveredParse (IProduction recoveryPointProduction) {
            Production
                .Setup (p => p.Parse (Source, 0, Context, SourceString, Environment))
                .Throws(new SyntaxErrorRecoveryException (RecoveryParseResult, recoveryPointProduction));
        }
    }
}